#include<iostream>
#include <vector>
#include <sstream>
#include <string>
#include <random>
#include <ctime>
#include <algorithm>
#include <unistd.h>
#include <Eigen/Core>

#include "tabu.h"
#include "utils.h"

using namespace Eigen;
using namespace std;
#if 0
std::string dataPath = R"(D:\program\cpp\BiSection\bisection\data\)";
#elif 0
std::string dataPath = R"(E:\program\c++\bi-section\data\)";
#elif 1
std::string dataPath = "";
#endif

/**
 * @brief readMat 读取数据文件
 * @param matData
 */
void readMat(int *matData)
{
    char tpData[1024] = {0};
    char *ret = nullptr;

    FILE *fp = fopen((dataPath + "p3000.1.dat").c_str(),"rb");
    string data;
    int nIndex = 0;
    int maxData = 0,minData = 0,tpNum,nSum = 0;

    //jump first line
    ret = fgets(tpData,1024,fp);
    do
    {
        ret = fgets(tpData,1024,fp);
        if(ret)
        {
            stringstream stream(ret);
            while(true){
                data = "";
                stream>>data;
                if(data.size()>0)
                {
                    tpNum = atoi(data.c_str());
                    maxData = maxData > tpNum ? maxData :tpNum;
                    minData = minData < tpNum ? minData : tpNum;
                    if(tpNum > 0)
                    {
                        nSum += tpNum;
                    }
                    matData[nIndex] = tpNum;
                    ++nIndex;
                }
                else
                {
                    break;
                }
            }
        }
    }
    while(ret);

    printf("max: %d min: %d max val:%d\n",maxData,minData,nSum);

    fclose(fp);
}

/**
 * @brief solveTest 按照特殊规则进行初始化
 * @param mat
 * @param solveMat
 */
void solveTest(MatrixX<int> &mat,MatrixX<int> &solveMat)
{
    std::vector<std::pair<int,int>> calcLevel;
    calcLevel.resize(3000,std::pair<int,int>(0,0));

    for(int i = 0; i < 3000; i++)
    {
        for(int j = 0; j < 2999; j+=2)
        {
            calcLevel[i].first += mat.coeff(i,j) + mat.coeff(i,j + 1);
            calcLevel[i].second = i;
        }
    }

    for(int i = 0; i < 3000; i++)
    {
        for(int j = 0; j < 2999; j+=2)
        {
            calcLevel[i].first += mat.coeff(j,i) + mat.coeff(j + 1,i);
        }
    }

    using DataItem = std::pair<int,int>;
    std::sort(calcLevel.begin(),calcLevel.end(),[](DataItem &item0,DataItem &item1){
        return item0.first > item1.first;
    });

    for(int i = 0; i < 1500; i++)
    {
        (solveMat.data())[calcLevel[i].second] = 1;
    }
}

/**
 * @brief initSolve 使用随机的方式进行初始化
 * @param solveMat
 */
void initSolve(MatrixX<int> &solveMat)
{
    int nCount = 0,restNum = 0;
    int *solvePtr = solveMat.data();
    default_random_engine randNum(time(0));
    //uniform_int_distribution<unsigned> u(0, 1);
    bernoulli_distribution u;
    for(int i = 0; i < 3000; i++)
    {
        restNum = 3000 - i;
        if(1500 - nCount==3000 - i)
        {
            for(int j = i; j < 3000; j++)
            {
                solvePtr[j] = 1;
            }
            break;
        }
        else if(nCount == 1500)
        {
            memset(solvePtr + i, 0, restNum * sizeof(int));
            break;
        }
        else
        {
            solvePtr[i] = u(randNum);
            nCount += solvePtr[i];
        }
    }

    nCount = 0;
    for(int i = 0; i < solveMat.size(); i++)
    {
        nCount += solvePtr[i];
    }
}

struct ArgStruct
{
    int tabuSize = 30;
    int neighborSize = 20;
    int calcTime = 400;
    //继续算上一次的
    bool calcLast = false;
};

/**
 * @brief getArgs
 * @param argc
 * @param argv
 * @return 禁忌表大小，邻域大小，迭代次数
 */
ArgStruct getArgs(int argc, char **argv)
{
    ArgStruct retData;

    if(argc < 4)
    {
        return retData;
    }

    retData.tabuSize = std::stoi(argv[1]);
    retData.neighborSize = std::stoi(argv[2]);
    retData.calcTime = std::stoi(argv[3]);

    if(retData.neighborSize > 1500)
    {
        retData.neighborSize = 1500;
    }

    if(argc > 4)
    {
        retData.calcLast = std::stoi(argv[4]);
    }
    else {
        retData.calcLast = false;
    }

    return retData;
}

int main(int argc, char *argv[])
{
    auto parseArgs = getArgs(argc,argv);
    printf("tabuSize:%d,neighborSize:%d,calcTimes:%d \n",parseArgs.tabuSize,parseArgs.neighborSize,parseArgs.calcTime);

    Tabu tabu(parseArgs.tabuSize,parseArgs.neighborSize);
    int *solvePtr = nullptr;
    int *pPtr = nullptr;
    MatrixX<int> solveMat;
    MatrixX<int> mat;
    clock_t startT,endT;

    startT = clock();
    solveMat.resize(1,3000);
    mat.resize(3000,3000);
    pPtr = mat.data();
    solvePtr = solveMat.data();

    solveMat.setZero();

    readMat(pPtr);
#if 1 //初始化值的方式
    solveTest(mat,solveMat);
#else
    initSolve(solveMat);
#endif

#if 1 //对读取上一次的值继续计算
    if(parseArgs.calcLast)
    {
        cout<<"continue last calc:"<<endl;
        solveMat = Utils::readSolve(R"(best.txt)");
    }
    else
    {
        cout<<"calc new:"<<endl;
    }
#endif

    MatrixXi ret = solveMat * mat * solveMat.transpose();
    int nStartVal = ret.coeff(0,0);
    int countNum = Utils::countOneSize(solveMat);
    printf("start val: %d one num: %d \n",nStartVal,countNum);
    tabu.run(mat,solveMat,parseArgs.calcTime);
    endT = clock();

    clock_t usedTime = endT - startT;
    auto lastVal = solveMat * mat * solveMat.transpose();

    time_t curTime = time(0);
    FILE *fp=fopen("recore.txt","ab+");
    countNum = Utils::countOneSize(solveMat);
    Utils::outResult(lastVal.coeff(0,0),solveMat,to_string(curTime)+ string(".txt"));
    fprintf(fp,"used time:%d ms start val: %d max value:%d result:%I64d.txt one num:%d\n",usedTime,nStartVal,lastVal.coeff(0,0),curTime,countNum);
    fclose(fp);

    printf("used time:%d ms start val: %d max value:%d result:%I64d.txt one num:%d\n",usedTime,nStartVal,lastVal.coeff(0,0),curTime,countNum);

    return 0;
}
